"""
下载指定书籍的所有章节
"""
import requests
import re
import time
import random
from pathlib import Path
from typing import Dict, Optional
import json
from concurrent.futures import ThreadPoolExecutor, as_completed
from threading import Lock

# 书籍类型字典
BOOK_TYPE_DICT = {
    1: "穷通宝鉴",
    2: "滴天髓",
    3: "三命通会",
    4: "八字提要",
    5: "子平真诠",
    6: "渊海子平",
    7: "天元巫咸",
    8: "神峰通考",
    9: "千里命稿",
    10: "五行精纪",
    11: "李虚中命书"
}

BASE_URL = "https://bzapi3.iwzbz.com/newguji/v2/gjbook/getnewguji3.php"


class BookDownloader:
    """书籍下载器"""
    
    def __init__(self, book_type: int, i_value: int = 1, output_dir: str = "downloads"):
        """
        初始化下载器
        
        Args:
            book_type: 书籍类型(b值，1-11)
            i_value: 类别(i值，默认1)
            output_dir: 输出目录
        """
        self.book_type = book_type
        self.i_value = i_value
        self.book_name = BOOK_TYPE_DICT.get(book_type, f"未知书籍{book_type}")
        # 所有类别文件都在同一个目录
        self.output_dir = Path(output_dir) / f"b{book_type:02d}_{self.book_name}"
        self.output_dir.mkdir(parents=True, exist_ok=True)
        
        self.session = requests.Session()
        self.session.headers.update({
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
        })
        
        self.download_stats = {
            'success': 0,
            'failed': 0,
            'failed_pages': [],
            'max_page': 0,
            'i_value': i_value
        }
        # 线程锁，用于统计信息更新
        self.stats_lock = Lock()
    
    def download_page(self, page_num: int) -> tuple:
        """
        下载指定页码
        
        Args:
            page_num: 页码
            
        Returns:
            (成功标志, HTML内容, 标题)
        """
        params = {
            'guid': 'f870456b-51cb-4144-aeea-33a0cb220756&',
            'b': self.book_type,
            'i': self.i_value,
            'p': page_num,
            't': random.random()
        }
        
        try:
            response = self.session.get(BASE_URL, params=params, timeout=15)
            response.raise_for_status()
            
            # 检查响应是否有效
            if len(response.text) < 1000:
                return False, None, None
            
            # 提取标题
            title_match = re.search(r'<title>(.*?)</title>', response.text)
            title = title_match.group(1) if title_match else f"第{page_num}页"
            
            return True, response.text, title
            
        except Exception as e:
            print(f"   ❌ 页码 {page_num} 请求失败: {e}")
            return False, None, None
    
    def save_page(self, page_num: int, html_content: str, title: str) -> bool:
        """
        保存页面到文件
        
        Args:
            page_num: 页码
            html_content: HTML内容
            title: 页面标题
            
        Returns:
            成功返回True
        """
        # 清理文件名中的特殊字符
        safe_title = re.sub(r'[<>:"/\\|?*]', '_', title)
        safe_title = safe_title.replace(' ', '_')[:50]  # 限制长度
        
        # 统一命名格式：包含 i 值（所有类别都在同一目录）
        filename = f"b{self.book_type:02d}_{self.book_name}_i{self.i_value:02d}_p{page_num:03d}_{safe_title}.html"
        filepath = self.output_dir / filename
        
        try:
            with open(filepath, 'w', encoding='utf-8') as f:
                f.write(html_content)
            return True
        except Exception as e:
            print(f"   ❌ 保存失败: {e}")
            return False
    
    def find_max_pages(self, start_page: int = 1, max_test: int = 10000) -> int:
        """
        查找书籍的最大页码（支持大量页码的智能查找）
        
        策略：
        1. 先快速测试较大值（100, 500, 1000, 2000...）确定大致范围
        2. 找到有效范围后，使用二分查找精确定位最大值
        3. 支持自适应扩大搜索范围
        
        Args:
            start_page: 起始页码
            max_test: 最大测试页码（默认10000，支持几千页的情况）
            
        Returns:
            最大有效页码
        """
        print(f"🔍 正在查找 {self.book_name} 的最大页码...")
        print(f"   搜索范围: 1 - {max_test}")
        print()
        
        # 阶段1: 快速定位有效范围 - 测试较大的跳跃值
        print("📊 阶段1: 快速定位有效范围...")
        
        # 测试序列：从小到大，逐步增大间隔
        test_sequence = [
            # 基础测试（确保前面部分有效）
            1, 10, 50, 100,
            # 中等范围测试
            200, 300, 400, 500,
            # 大范围测试（支持几千页）
            1000, 2000, 3000, 4000, 5000,
            6000, 7000, 8000, 9000, 10000
        ]
        
        valid_pages = []
        first_invalid = None
        
        for page_num in test_sequence:
            if page_num > max_test:
                break
            
            success, html, title = self.download_page(page_num)
            if success:
                valid_pages.append(page_num)
                print(f"   ✅ p={page_num:5d}: {title}")
                if page_num > self.download_stats['max_page']:
                    self.download_stats['max_page'] = page_num
            else:
                print(f"   ❌ p={page_num:5d}: 无效")
                if first_invalid is None:
                    first_invalid = page_num
                    # 找到第一个无效页码，可以缩小搜索范围
                    break
            
            time.sleep(0.05)  # 快速定位时减少延迟
        
        # 如果没有找到有效页码，返回0
        if not valid_pages:
            print("   ⚠️  未找到有效页码")
            return 0
        
        # 确定搜索范围
        max_valid = max(valid_pages)
        
        # 如果找到了第一个无效页码，使用它作为上限
        if first_invalid:
            high = first_invalid
        else:
            # 如果没有找到无效页码，说明测试的最大值仍然有效，需要扩大搜索
            print(f"   ⚠️  测试的最大值 {max_test} 仍有效，可能需要扩大搜索范围")
            high = max_test + 1
        
        # 阶段2: 二分查找精确定位最大值
        print()
        print(f"📊 阶段2: 使用二分查找精确定位最大值...")
        print(f"   搜索范围: {max_valid} - {high}")
        
        low = max_valid
        last_valid = max_valid
        
        # 二分查找
        iteration = 0
        max_iterations = 50  # 最多迭代50次，防止死循环
        
        while low < high and iteration < max_iterations:
            iteration += 1
            mid = (low + high) // 2
            
            success, html, title = self.download_page(mid)
            
            if success:
                last_valid = mid
                low = mid + 1
                print(f"   [{iteration:2d}] p={mid:5d}: ✅ 有效 - {title[:40]}")
            else:
                high = mid
                print(f"   [{iteration:2d}] p={mid:5d}: ❌ 无效")
            
            time.sleep(0.05)  # 二分查找时减少延迟
        
        max_page = last_valid
        
        if iteration >= max_iterations:
            print(f"   ⚠️  达到最大迭代次数，返回最后有效页码")
        
        print()
        print(f"   ✅ 最终确定的最大页码: {max_page}")
        
        # 验证最后几页确保准确
        print()
        print("📊 阶段3: 验证边界页码...")
        for p in range(max_page - 2, max_page + 4):
            if p > 0:
                success, html, title = self.download_page(p)
                status = "✅" if success else "❌"
                print(f"   {status} p={p}: {'有效' if success else '无效'}")
                time.sleep(0.05)  # 验证边界时减少延迟
        
        return max_page
    
    def find_all_i_categories(self, max_test: int = 20) -> list:
        """
        查找指定书籍的所有有效 i 类别
        
        Args:
            max_test: 最大测试 i 值（默认20）
            
        Returns:
            有效的 i 值列表
        """
        valid_i_list = []
        
        for i_value in range(1, max_test + 1):
            params = {
                'guid': '',
                'b': self.book_type,
                'i': i_value,
                'p': 1,
                't': random.random()
            }
            
            try:
                response = self.session.get(BASE_URL, params=params, timeout=10)
                
                # 检查响应是否有效
                if len(response.text) >= 1000:
                    # 提取标题
                    title_match = re.search(r'<title>(.*?)</title>', response.text)
                    # 检查内容
                    if title_match and 'content_item' in response.text:
                        valid_i_list.append(i_value)
                else:
                    # 如果连续3个无效，可能已经到最大值了，停止测试
                    if i_value > 3 and len(valid_i_list) > 0 and (i_value - valid_i_list[-1]) > 3:
                        break
                
                time.sleep(0.05)  # 查找i类别时减少延迟
            except Exception:
                # 如果连续3个无效，可能已经到最大值了，停止测试
                if i_value > 3 and len(valid_i_list) > 0 and (i_value - valid_i_list[-1]) > 3:
                    break
        
        return valid_i_list
    
    def download_all_pages(self, start_page: int = 1, end_page: Optional[int] = None):
        """
        下载所有页码
        
        Args:
            start_page: 起始页码（默认1）
            end_page: 结束页码（如果不指定，会自动查找）
        """
        print("=" * 80)
        if self.i_value > 1:
            print(f"🚀 开始下载: {self.book_name} (b={self.book_type}, i={self.i_value})")
        else:
            print(f"🚀 开始下载: {self.book_name} (b={self.book_type})")
        print("=" * 80)
        print(f"📁 输出目录: {self.output_dir.absolute()}")
        print()
        
        # 如果没有指定结束页码，自动查找
        if end_page is None:
            end_page = self.find_max_pages()
            if end_page == 0:
                print("❌ 未找到有效页码，无法下载")
                return
            print()
        
        print(f"📊 页码范围: {start_page} - {end_page}")
        print()
        
        # 使用并发下载提升速度
        total_pages = end_page - start_page + 1
        max_workers = min(10, total_pages)  # 最多10个并发线程
        print(f"🚀 使用 {max_workers} 个并发线程加速下载...")
        print()
        
        # 下载所有页码（并发）
        with ThreadPoolExecutor(max_workers=max_workers) as executor:
            # 提交所有下载任务
            future_to_page = {
                executor.submit(self._download_and_save_page, page_num): page_num 
                for page_num in range(start_page, end_page + 1)
            }
            
            # 处理完成的任务
            completed = 0
            
            for future in as_completed(future_to_page):
                page_num = future_to_page[future]
                completed += 1
                
                try:
                    result = future.result()
                    if result['success']:
                        print(f"📥 [{completed}/{total_pages}] 页码 {page_num}: ✅ {result['title'][:40]}")
                    else:
                        print(f"📥 [{completed}/{total_pages}] 页码 {page_num}: ❌ {result.get('error', '失败')}")
                except Exception as e:
                    print(f"📥 [{completed}/{total_pages}] 页码 {page_num}: ❌ 异常 - {e}")
                    with self.stats_lock:
                        self.download_stats['failed'] += 1
                        self.download_stats['failed_pages'].append(page_num)
        
        # 输出统计信息
        print()
        print("=" * 80)
        print("📊 下载统计")
        print("=" * 80)
        print(f"✅ 成功: {self.download_stats['success']} 页")
        print(f"❌ 失败: {self.download_stats['failed']} 页")
        
        if self.download_stats['failed_pages']:
            print(f"⚠️  失败页码: {self.download_stats['failed_pages'][:20]}")
            if len(self.download_stats['failed_pages']) > 20:
                print(f"   ... 还有 {len(self.download_stats['failed_pages']) - 20} 页失败")
        
        print(f"📁 文件保存在: {self.output_dir.absolute()}")
        print("=" * 80)
        
        # 保存下载统计
        stats_file = self.output_dir / "download_stats.json"
        with open(stats_file, 'w', encoding='utf-8') as f:
            json.dump(self.download_stats, f, ensure_ascii=False, indent=2)
        
        print(f"📝 统计信息已保存到: {stats_file}")
    
    def _download_and_save_page(self, page_num: int) -> dict:
        """
        下载并保存单个页面（用于并发下载）
        
        Args:
            page_num: 页码
            
        Returns:
            结果字典
        """
        success, html_content, title = self.download_page(page_num)
        
        if success and html_content:
            # 保存文件
            if self.save_page(page_num, html_content, title):
                with self.stats_lock:
                    self.download_stats['success'] += 1
                return {'success': True, 'title': title, 'page_num': page_num}
            else:
                with self.stats_lock:
                    self.download_stats['failed'] += 1
                    self.download_stats['failed_pages'].append(page_num)
                return {'success': False, 'error': '保存失败', 'page_num': page_num}
        else:
            with self.stats_lock:
                self.download_stats['failed'] += 1
                self.download_stats['failed_pages'].append(page_num)
            return {'success': False, 'error': '下载失败', 'page_num': page_num}


def download_book_all_categories(book_type: int, output_dir: str = "downloads"):
    """
    下载指定书籍的所有类别（i值）和所有页码
    
    Args:
        book_type: 书籍类型(b值，1-11)
        output_dir: 输出目录
    """
    book_name = BOOK_TYPE_DICT.get(book_type, f"未知书籍{book_type}")
    
    print("=" * 80)
    print(f"📚 开始下载书籍: {book_name} (b={book_type})")
    print("=" * 80)
    print()
    
    # 创建临时下载器来查找所有类别
    temp_downloader = BookDownloader(book_type=book_type, i_value=1, output_dir=output_dir)
    print(f"🔍 正在查找所有类别...")
    valid_i_list = temp_downloader.find_all_i_categories(max_test=20)
    
    if not valid_i_list:
        print(f"❌ 未找到有效的类别")
        return
    
    print(f"✅ 找到 {len(valid_i_list)} 个类别: {valid_i_list}")
    print()
    
    # 下载每个类别的所有页码
    all_results = {}
    
    for i_value in valid_i_list:
        print("\n" + "=" * 80)
        print(f"📖 类别 {i_value}/{len(valid_i_list)}: i={i_value}")
        print("=" * 80)
        
        downloader = BookDownloader(book_type=book_type, i_value=i_value, output_dir=output_dir)
        downloader.download_all_pages(start_page=1)
        
        all_results[i_value] = {
            'success': downloader.download_stats['success'],
            'failed': downloader.download_stats['failed'],
            'max_page': downloader.download_stats['max_page'],
            'failed_pages': downloader.download_stats['failed_pages']
        }
        
        # 类别之间的延迟（优化：减少延迟）
        if i_value < valid_i_list[-1]:
            print(f"\n⏸️  等待0.5秒后继续下载下一类别...")
            time.sleep(0.5)
    
    # 输出总统计
    print("\n" + "=" * 80)
    print(f"📊 {book_name} 所有类别下载统计")
    print("=" * 80)
    
    total_success = sum(r['success'] for r in all_results.values())
    total_failed = sum(r['failed'] for r in all_results.values())
    total_pages = sum(r['max_page'] for r in all_results.values())
    
    print(f"\n✅ 总成功: {total_success} 页")
    print(f"❌ 总失败: {total_failed} 页")
    print(f"📄 最大页码总计: {total_pages}")
    
    for i_value, result in all_results.items():
        print(f"\n类别 i={i_value}:")
        print(f"   ✅ 成功: {result['success']} 页 (最大页码: {result['max_page']})")
        if result['failed'] > 0:
            print(f"   ❌ 失败: {result['failed']} 页")
    
    print("=" * 80)
    
    # 返回书籍统计信息
    return {
        'book_type': book_type,
        'book_name': book_name,
        'success': total_success,
        'failed': total_failed,
        'max_page': total_pages,
        'categories': all_results
    }


def download_all_books(output_dir: str = "downloads"):
    """
    下载所有书籍（b=1到11）的所有类别
    
    Args:
        output_dir: 输出目录
    """
    print("=" * 80)
    print("📚 开始下载所有书籍（b=1 到 11）")
    print("=" * 80)
    print()
    
    total_results = {}
    
    # 遍历所有书籍类型（1-11）
    for book_type in range(1, 12):
        book_name = BOOK_TYPE_DICT.get(book_type, f"未知书籍{book_type}")
        
        print("\n" + "=" * 80)
        print(f"📖 书籍 {book_type}/11: {book_name} (b={book_type})")
        print("=" * 80)
        
        try:
            result = download_book_all_categories(book_type=book_type, output_dir=output_dir)
            
            # 使用返回的统计信息
            if result:
                total_results[book_type] = {
                    'name': book_name,
                    'success': result.get('success', 0),
                    'failed': result.get('failed', 0),
                    'max_page': result.get('max_page', 0)
                }
            else:
                # 如果没有找到有效类别，记录为空结果
                total_results[book_type] = {
                    'name': book_name,
                    'success': 0,
                    'failed': 0,
                    'max_page': 0,
                    'error': '未找到有效类别'
                }
            
            # 书籍之间的延迟
            if book_type < 11:
                print(f"\n⏸️  等待1秒后继续下载下一本书籍...")
                time.sleep(1)
                
        except Exception as e:
            print(f"❌ 下载 {book_name} (b={book_type}) 时出错: {e}")
            total_results[book_type] = {
                'name': book_name,
                'success': 0,
                'failed': 0,
                'max_page': 0,
                'error': str(e)
            }
    
    # 输出总统计
    print("\n" + "=" * 80)
    print("📊 所有书籍下载统计")
    print("=" * 80)
    
    total_success = sum(r.get('success', 0) for r in total_results.values())
    total_failed = sum(r.get('failed', 0) for r in total_results.values())
    total_pages = sum(r.get('max_page', 0) for r in total_results.values())
    
    print(f"\n✅ 总成功: {total_success} 页")
    print(f"❌ 总失败: {total_failed} 页")
    print(f"📄 最大页码总计: {total_pages}")
    print()
    
    for book_type, result in sorted(total_results.items()):
        book_name = result.get('name', f'未知书籍{book_type}')
        success = result.get('success', 0)
        failed = result.get('failed', 0)
        max_page = result.get('max_page', 0)
        
        print(f"📖 b={book_type:2d} {book_name}:")
        print(f"   ✅ 成功: {success} 页 (最大页码: {max_page})")
        if failed > 0:
            print(f"   ❌ 失败: {failed} 页")
        if 'error' in result:
            print(f"   ⚠️  错误: {result['error']}")
    
    print("=" * 80)


def main():
    """主函数"""
    import sys
    
    # 如果提供了命令行参数
    if len(sys.argv) > 1:
        arg = sys.argv[1].lower()
        
        # 如果参数是 "all"，下载所有书籍
        if arg == "all":
            download_all_books(output_dir="downloads")
            return
        
        # 否则尝试解析为书籍类型
        try:
            book_type = int(sys.argv[1])
            if book_type < 1 or book_type > 11:
                print(f"❌ 书籍类型必须在 1-11 之间")
                return
            download_book_all_categories(book_type=book_type, output_dir="downloads")
        except ValueError:
            print(f"❌ 无效的书籍类型: {sys.argv[1]}")
    else:
        # 默认下载所有书籍
        print("💡 用法: python download_book.py <book_type|all>")
        print("   例如: python download_book.py 1     (下载穷通宝鉴)")
        print("   例如: python download_book.py all   (下载所有书籍)")
        print()
        print("📚 书籍类型:")
        for b, name in BOOK_TYPE_DICT.items():
            print(f"   b={b}: {name}")
        print()
        print("🚀 开始下载所有书籍...")
        print()
        download_all_books(output_dir="downloads")


if __name__ == '__main__':
    main()

